package net.avcompris.base.testutil.processes;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * You should inherit this class when you want to use a process in your tests.
 * 
 * @author David Andrianavalontsalama
 * 
 * @param <T> the type of objects to be set as "current" during processing.
 * @param <U> the type the process returns.
 */
public abstract class AbstractProcess<T, U> {

	/**
	 * This method is to be overrided for process initialization, if needed.
	 * <p>
	 * Symmetric method is {@link #release()}.
	 */
	public void init() throws Exception {

		// do nothing
	}

	/**
	 * Set the test class above this process.
	 */
	final void setTests(final TestsWithProcessesBefore tests) {

		this.tests = checkNotNull(tests, "tests");
	}

	private TestsWithProcessesBefore tests;

	/**
	 * This method is to be overrided to release resources after process is
	 * done, if
	 * needed.
	 * <p>
	 * Symmetric method is {@link #init()}.
	 */
	public void release() throws Exception {

		// do nothing
	}

	/**
	 * This method is to be overrided to execute the process.
	 */
	public abstract void execute() throws Exception;

	/**
	 * Process implementations must call this protected method
	 * to set the "current" object that tests annotated
	 * with @{@link WhileProcessing} will want to use via
	 * the {@link TestsWithProcessesBefore#getProcessCurrent(Class)} method.
	 * 
	 * @param current the "current" object to pass to tests.
	 */
	protected final void setCurrent(final T current) {

		if (tests == null) {

			throw new IllegalStateException(
					"Tests ref has not been initialized.");
		}

		currentIndex = cliProgress.inc();

		this.current = new ProcessCurrent<T>(currentIndex, current);

		final ProcessEntry processEntry;

		if (current != null && current instanceof ProcessEntry) {

			processEntry = (ProcessEntry) current;

		} else {

			processEntry = null;
		}

		tests.runTests(this, processEntry);
	}

	final CliProgress cliProgress = new CliProgress();

	private ProcessCurrent<T> current;

	final int getCurrentIndex() {

		return currentIndex;
	}

	private int currentIndex;

	/**
	 * This method is to be overrided for processes to return their results.
	 * 
	 * @return the result of the process.
	 */
	public abstract U getResult();

	final T getCurrent() {

		if (current == null) {

			throw new IllegalStateException(
					"Current has not been set."
							+ " Probable cause is the Process instance did not call the setCurrent() method"
							+ " while processing.");
		}

		return current.current;
	}
}
